11. Bonus Question: Parallel Requests

Bonus Question: Parallel Requests

This quiz was going to be the last quiz in the course. It's a bit crazy. And it's difficult. And in all reality you'll probably never need to (or want to!) write anything like this in real life.

But! As I was discussing it with Art, our Director of Engineering, he asked me where he could find the code so that he could turn this challenge into an interview question. So I figured, "why not?"

Here it is as a bonus question.

Requirements

I want you to write code that is capable of:

  • requesting planet data and creating planet thumbnails using Promises (just like you've done for the whole second lesson).
  • executing the network requests in parallel (similar to the .map and Promise.all() quizzes).
  • creating the thumbnails in the same order as the Promises were created (similar to the .forEach and Promise.all() quizzes).
  • not waiting for all network requests to settle before creating thumbnails. Thumbnails should be created as soon as all of the Promises before them have settled (a combination of all the array quizzes).

Feel free to change any or all of the helper methods (like createPlanetThumb)!

As an example, here's an array of Promises:

getJSON(url1).then(createPlanetThumb)
.then(function(){
  return getJSON(url2).then(createPlanetThumb);
}
.then(function(){
  return getJSON(url3).then(createPlanetThumb);
}
.then(function(){
  return getJSON(url4).then(createPlanetThumb);
}
.then(function(){
  return getJSON(url5).then(createPlanetThumb);
}

All of the getJSONs should execute in parallel. But, if url3 arrives first, it should wait for url1 to resolve and url2 to resolve before creating its thumbnail. The state of url4 and url5 don't affect url3. In other words, each .then executes as soon as all of the .thens before it resolve, yet all of the network requests are executed in parallel.

Words of Encouragement and Acknowledgement

This is a combination of the last few array method quizzes. It's tough but you've already used all of the methods and techniques you'll need to conquer this quiz. You can do it!

I recommend using network throttling too. Use 3G and you'll see some jitter with respect to the order in which requests finish.

Also, I can't say I came up with this quiz on my own. This quiz, and much of this course, was inspired by this guide to Promises by Jake Archibald. He also reviewed this course and helped me with a few inaccuracies with the first draft of the script. If you scan through the article, you'll find the general strategy for solving this quiz. Don't look if you want a challenge!

Checkout the bonus-start branch to get started! Good luck!

Task Description:

Requests in parallel, but execution in series.

Task List:

Task Feedback:

Excellent work!

Solution

Here's my solution! There are a few ways of doing this and I wanted to keep my logic as obvious as possible. I made changes to createPlanetThumb (it's now a Promise) and getJSON (for testing purposes) as well. And I added some logging to get a clearer picture of the rendering process.

Here are the relevant changes (you can always checkout bonus-solution to see it too). Underneath, you'll find a discussion of my overall strategy.

/**
 * Helper function to create a planet thumbnail - Promisified version!
 * @param  {Object} data - The raw data describing the planet.
 */
function createPlanetThumb(data) {
  return new Promise(function(resolve) {
    var pT = document.createElement('planet-thumb');
    for (var d in data) {
      pT[d] = data[d];
    }
    home.appendChild(pT);
    console.log('rendered: ' + data.pl_name);
    resolve();
  });
}

/**
 * Performs an XHR for a JSON and returns a parsed JSON response - with a delay!
 * @param  {String} url - The JSON URL to fetch.
 * @return {Promise}    - A promise that passes the parsed JSON response.
 */
function getJSON(url) {
  console.log('sent: ' + url);
  return get(url).then(function(response) {
    // For testing purposes, I'm making sure that the urls don't return in order
    if (url === 'data/planets/Kepler-62f.json') {
      return new Promise(function(resolve) {
        setTimeout(function() {
          console.log('received: ' + url);
          resolve(response.json());
        }, 500);
      });
    } else {
      console.log('received: ' + url);
      return response.json();
    }
  });
}

window.addEventListener('WebComponentsReady', function() {
  home = document.querySelector('section[data-route="home"]');

  getJSON('../data/earth-like-results.json')
  .then(function(response) {
    addSearchHeader(response.query);
    return response;
  })
  .then(function(response) {
    var sequence = Promise.resolve();

    // .map executes all of the network requests immediately.
    var arrayOfExecutingPromises = response.results.map(function(result) {
      return getJSON(result);
    });

    arrayOfExecutingPromises.forEach(function(request) {
      // Loop through the pending requests that were returned by .map (and are in order) and
      // turn them into a sequence.
      // request is a getJSON() that's currently executing.
      sequence = sequence.then(function() {
        // Remember that createPlanetThumb is a Promise, so it must resolve before Promises
        // later in the sequence can execute.
        return request.then(createPlanetThumb);
      });
    });
  });
});

And here's how I know it's working:

This is a three step process:

  1. Create an array of network requests that are executing in parallel.
  2. Attach a Promisified version of createPlanetThumb to each request in order to render thumbnails.
  3. Create a sequence of Promises, each of which is a request and thumbnail rendering. I imagine it like this (each '-->' is a .then):

[ (request --> render) --> (request --> render) --> (request --> render) --> … ]

The requests can resolve whenever, but the renders will block the sequence because they're now Promises that resolve only after the thumbnail has been created. Each render must wait for all of the renders before it to resolve before executing.

.map takes care of the parallel requests part and .forEach chains the requests into a sequence.

There are other ways of writing this more succinctly, perhaps using .reduce. Either way, this code works and I'm happy. How did your solution compare to mine? Share your solutions in the forums!

Thanks for being a part of this class!